﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using EnvDTE;
using eVision.SharpSort.Core;
using EnvDTE80;
using System.IO;

namespace eVision.SharpSort
{
    public class CodeMemberInfo
    {
        private static Regex commentLineRegex = new Regex(@"^\s*//[^\u00A0][^\r\n]*$", RegexOptions.Singleline | RegexOptions.Compiled);
        private static Regex startOfLineRegex = new Regex(@"^\s*$", RegexOptions.Singleline | RegexOptions.Compiled);

        public CodeMemberInfo() { }
        public CodeMemberInfo(CodeElement codeElement, ICollection<TextRange> textRanges)
        {
            Guard.IsNotNull(codeElement, "codeElement");

            bool render;
            this.Code = GetElementCode(codeElement, textRanges, out render);
            Render = render;
            this.CodeElement = codeElement;
            
            CodeFormatter.MemberType memberType = CodeFormatter.GetMemberFromCodeElementType(codeElement);

            switch (memberType)
            {
                case CodeFormatter.MemberType.Class:
                    CodeClass codeClass = (CodeClass)codeElement;
                    this.AccessModifier = GetAccessModifier(codeClass.Access);
                    this.IsStatic = false;
                    this.Name = codeClass.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = "";
                    this.EntityType = EntityTypeEnum.Class;
                    break;
                case CodeFormatter.MemberType.Struct:
                    CodeStruct codeStruct = (CodeStruct)codeElement;
                    this.AccessModifier = GetAccessModifier(codeStruct.Access);
                    this.IsStatic = false;
                    this.Name = codeStruct.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = "";
                    this.EntityType = EntityTypeEnum.Struct;
                    break;
                case CodeFormatter.MemberType.Interface:
                    CodeInterface codeInterface = (CodeInterface)codeElement;
                    this.AccessModifier = GetAccessModifier(codeInterface.Access);
                    this.IsStatic = false;
                    this.Name = codeInterface.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = "";
                    this.EntityType = EntityTypeEnum.Interface;
                    break;
                case CodeFormatter.MemberType.Enum:
                    CodeEnum codeEnum = (CodeEnum)codeElement;
                    this.AccessModifier = GetAccessModifier(codeEnum.Access);
                    this.IsStatic = true;
                    this.Name = codeEnum.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = "";
                    this.EntityType = EntityTypeEnum.Enum;
                    break;
                case CodeFormatter.MemberType.Field:
                    CodeVariable codeField = (CodeVariable)codeElement;
                    this.AccessModifier = GetAccessModifier(codeField.Access);
                    this.IsStatic = codeField.IsShared;
                    this.Name = codeField.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = codeField.Type.AsFullName;
                    this.EntityType = EntityTypeEnum.Field;
                    break;
                case CodeFormatter.MemberType.Delegate:
                    CodeDelegate2 codeDelegate = (CodeDelegate2)codeElement;
                    this.AccessModifier = GetAccessModifier(codeDelegate.Access);
                    this.IsStatic = true;
                    this.Name = codeDelegate.Name;
                    this.NumberOfParameters = codeDelegate.Parameters.Count;
                    this.EntityVariableType = "";
                    this.EntityType = EntityTypeEnum.Delegate;
                    break;
                case CodeFormatter.MemberType.Event:
                    CodeEvent codeEvent = (CodeEvent)codeElement;
                    this.AccessModifier = GetAccessModifier(codeEvent.Access);
                    this.IsStatic = codeEvent.IsShared;
                    this.Name = codeEvent.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = codeEvent.Type.AsFullName;
                    this.EntityType = EntityTypeEnum.Event;
                    break;
                case CodeFormatter.MemberType.Property:
                    CodeProperty codeProperty = (CodeProperty)codeElement;
                    this.AccessModifier = GetAccessModifier(codeProperty.Access);
                    this.IsStatic = (codeProperty.Setter != null && codeProperty.Setter.IsShared)
                        || (codeProperty.Getter != null && codeProperty.Getter.IsShared);
                    this.Name = codeProperty.Name;
                    this.NumberOfParameters = 0;
                    this.EntityVariableType = "";
                    this.EntityType = EntityTypeEnum.Property;
                    break;
                case CodeFormatter.MemberType.Constructor:
                case CodeFormatter.MemberType.Method:
                    CodeFunction codeFunction = (CodeFunction)codeElement;
                    this.AccessModifier = GetAccessModifier(codeFunction.Access);
                    this.IsStatic = codeFunction.IsShared;
                    this.Name = codeFunction.Name;
                    this.NumberOfParameters = codeFunction.Parameters.Count;
                    this.EntityVariableType = codeFunction.FunctionKind == vsCMFunction.vsCMFunctionConstructor ? "" : codeFunction.Type.AsFullName;
                    this.EntityType = codeFunction.FunctionKind == vsCMFunction.vsCMFunctionConstructor ? EntityTypeEnum.Constructor : EntityTypeEnum.Method;
                    break;
                default:
                    break;
            }
        }

        public static AccessModifierEnum GetAccessModifier(vsCMAccess access)
        {
            switch (access)
            {
                case vsCMAccess.vsCMAccessProject:
                case vsCMAccess.vsCMAccessAssemblyOrFamily:
                    return AccessModifierEnum.@internal;
                case vsCMAccess.vsCMAccessDefault:
                case vsCMAccess.vsCMAccessPrivate:
                    return AccessModifierEnum.@private;
                case vsCMAccess.vsCMAccessProjectOrProtected:
                case vsCMAccess.vsCMAccessProtected:
                    return AccessModifierEnum.@protected;
                case vsCMAccess.vsCMAccessWithEvents:
                case vsCMAccess.vsCMAccessPublic:
                    return AccessModifierEnum.@public;
                default:
                    throw new ArgumentException();
            }
        }

        private static int FirstIndexNotWhiteSpace(String value)
        {
            for (int i = 0; i < value.Length; ++i)
            {
                if (!Char.IsWhiteSpace(value[i]))
                    return i;
            }

            return value.Length;
        }

        private static void FindComments(EditPoint editPoint)
        {
            String line = null;

            if (editPoint.Line > 0)
            {
                EditPoint startOfLine = editPoint.CreateEditPoint();
                startOfLine.StartOfLine();
                line = startOfLine.GetText(editPoint);
                if (!startOfLineRegex.IsMatch(line))
                    return;
            }

            while (editPoint.Line > 0)
            {
                line = editPoint.GetLines(editPoint.Line - 1, editPoint.Line);
                if (commentLineRegex.IsMatch(line))
                {
                    editPoint.LineUp(1);
                    editPoint.MoveToLineAndOffset(editPoint.Line, FirstIndexNotWhiteSpace(line) + 1);
                }
                else
                {
                    break;
                }
            }
            
        }

        private static EditPoint GetStartPoint(CodeElement element)
        {
            EditPoint startPoint = element.GetStartPoint(vsCMPart.vsCMPartWholeWithAttributes).CreateEditPoint();
            FindComments(startPoint);
            return startPoint;
        }

        private static EditPoint GetEndPoint(CodeElement element)
        {
            EditPoint endPoint = element.GetEndPoint(vsCMPart.vsCMPartWholeWithAttributes).CreateEditPoint();
            return endPoint;
        }

        private static String GetElementCode(CodeElement element, ICollection<TextRange> textRanges, out bool render)
        {
            EditPoint startPoint = GetStartPoint(element);
            EditPoint endPoint = GetEndPoint(element);

            render = true;
            
            TextRange currentRange = new TextRange() {StartLine = startPoint.Line, StartColumn = startPoint.LineCharOffset, EndLine = endPoint.LineCharOffset + 1, EndColumn = endPoint.LineCharOffset };
            if (textRanges.Contains(currentRange))
            {
                render = false;
            }
            else
            {
                textRanges.Add(currentRange);
                render = true;
            }

            return startPoint.GetText(endPoint);
        }

        public void RemoveElementFromLayout()
        {
            if (Render)
            {
                EditPoint startPoint = GetStartPoint(CodeElement);
                EditPoint endPoint = GetEndPoint(CodeElement);

                startPoint.Delete(endPoint);
            }
        }

        public enum AccessModifierEnum { Invalid, @public, @private, @protected, @internal } ;
        public enum EntityTypeEnum { Namespace, Class, Struct, Interface, Enum, Field, Delegate, Event, Property, Constructor, Method };

        public AccessModifierEnum AccessModifier { get; set; }
        public bool IsStatic { get; set; }
        public String EntityVariableType { get; set; }
        public String Name { get; set; }
        public int NumberOfParameters { get; set; }
        public EntityTypeEnum EntityType { get; set; }
        public String Code { get; set; }
        public CodeElement CodeElement { get; set; }
        public bool Render { get; set; }
    }

}
